using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Making.Avalonia.WebView.Core.Abstractions;
using Making.Avalonia.WebView.Core.Models;
using Making.Avalonia.WebView.Linux.Native;
using Microsoft.Extensions.Logging;

namespace Making.Avalonia.WebView.Linux;

public class LinuxWebViewProvider : IWebViewProvider, IDisposable
{
    private readonly ILogger<LinuxWebViewProvider>? _logger;
    private readonly ConcurrentDictionary<IntPtr, LinuxWebView> _webViews = new();
    private readonly object _gtkLock = new();
    private static bool _gtkInitialized = false;
    private bool _disposed;

    public event EventHandler<WebResourceRequestedEventArgs>? WebResourceRequested;
    public event EventHandler<NavigationEventArgs>? NavigationStarting;
    public event EventHandler<NavigationEventArgs>? NavigationCompleted;

    public LinuxWebViewProvider(ILogger<LinuxWebViewProvider>? logger = null)
    {
        _logger = logger;
        InitializeGtk();
    }

    public IPlatformHandle CreateWebView(IPlatformHandle parent, WebViewConfiguration configuration)
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            throw new PlatformNotSupportedException("Linux WebView provider only supports Linux platform");

        _logger?.LogDebug("Creating Linux WebKitGTK WebView");

        lock (_gtkLock)
        {
            var webView = new LinuxWebView(this, configuration, _logger);
            var nativeHandle = webView.Initialize(parent);
            
            _webViews[nativeHandle.Handle] = webView;
            
            return nativeHandle;
        }
    }

    public void DestroyWebView(IPlatformHandle webView)
    {
        var handle = webView.Handle;
        
        if (_webViews.TryRemove(handle, out var linuxWebView))
        {
            _logger?.LogDebug("Destroying Linux WebKitGTK WebView");
            
            lock (_gtkLock)
            {
                linuxWebView.Destroy();
            }
        }
    }

    public async Task NavigateAsync(IPlatformHandle webView, Uri uri)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            _logger?.LogDebug("Navigating to {Uri}", uri);
            await linuxWebView.NavigateAsync(uri);
        }
    }

    public async Task<string> ExecuteScriptAsync(IPlatformHandle webView, string script)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            _logger?.LogDebug("Executing script in WebKitGTK WebView");
            return await linuxWebView.ExecuteScriptAsync(script);
        }
        
        return string.Empty;
    }

    public void SetZoomFactor(IPlatformHandle webView, double zoomFactor)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            _logger?.LogDebug("Setting zoom factor to {ZoomFactor}", zoomFactor);
            linuxWebView.SetZoomFactor(zoomFactor);
        }
    }

    public double GetZoomFactor(IPlatformHandle webView)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            return linuxWebView.GetZoomFactor();
        }
        
        return 1.0;
    }

    public bool CanGoBack(IPlatformHandle webView)
    {
        return _webViews.TryGetValue(webView.Handle, out var linuxWebView) && linuxWebView.CanGoBack();
    }

    public bool CanGoForward(IPlatformHandle webView)
    {
        return _webViews.TryGetValue(webView.Handle, out var linuxWebView) && linuxWebView.CanGoForward();
    }

    public void GoBack(IPlatformHandle webView)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            linuxWebView.GoBack();
            _logger?.LogDebug("WebView navigated back");
        }
    }

    public void GoForward(IPlatformHandle webView)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            linuxWebView.GoForward();
            _logger?.LogDebug("WebView navigated forward");
        }
    }

    public void Reload(IPlatformHandle webView)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            linuxWebView.Reload();
            _logger?.LogDebug("WebView reloaded");
        }
    }

    public void AddWebResourceFilter(IPlatformHandle webView, string filter)
    {
        if (_webViews.TryGetValue(webView.Handle, out var linuxWebView))
        {
            linuxWebView.AddWebResourceFilter(filter);
            _logger?.LogDebug("Added web resource filter: {Filter}", filter);
        }
    }

    internal void OnWebResourceRequested(WebResourceRequestedEventArgs args)
    {
        WebResourceRequested?.Invoke(this, args);
    }

    internal void OnNavigationStarting(NavigationEventArgs args)
    {
        NavigationStarting?.Invoke(this, args);
    }

    internal void OnNavigationCompleted(NavigationEventArgs args)
    {
        NavigationCompleted?.Invoke(this, args);
    }

    private void InitializeGtk()
    {
        if (!_gtkInitialized)
        {
            lock (_gtkLock)
            {
                if (!_gtkInitialized)
                {
                    _logger?.LogDebug("Initializing GTK");
                    
                    if (!WebKitGtkInterop.gtk_init_check(0, Array.Empty<string>()))
                    {
                        throw new InvalidOperationException("Failed to initialize GTK");
                    }
                    
                    _gtkInitialized = true;
                    _logger?.LogDebug("GTK initialized successfully");
                }
            }
        }
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            foreach (var kvp in _webViews.ToList())
            {
                DestroyWebView(new PlatformHandle(kvp.Key, "LinuxWebView"));
            }
            
            _disposed = true;
        }
    }
}

internal class LinuxWebView
{
    private readonly LinuxWebViewProvider _provider;
    private readonly WebViewConfiguration _configuration;
    private readonly ILogger? _logger;
    private IntPtr _webView = IntPtr.Zero;
    private IntPtr _container = IntPtr.Zero;
    private IntPtr _contentManager = IntPtr.Zero;

    public LinuxWebView(LinuxWebViewProvider provider, WebViewConfiguration configuration, ILogger? logger)
    {
        _provider = provider;
        _configuration = configuration;
        _logger = logger;
    }

    public IPlatformHandle Initialize(IPlatformHandle parent)
    {
        _logger?.LogDebug("Initializing Linux WebView with parent handle: {Handle}", parent.Handle);

        _contentManager = WebKitGtkInterop.webkit_user_content_manager_new();
        _webView = WebKitGtkInterop.webkit_web_view_new_with_user_content_manager(_contentManager);
        if (_webView == IntPtr.Zero)
            throw new InvalidOperationException("Failed to create WebKitGTK WebView");

        ApplyConfiguration();
        SetupScriptMessaging();

        CreateContainer();
        WebKitGtkInterop.gtk_container_add(_container, _webView);
        WebKitGtkInterop.gtk_widget_show_all(_container);
        WebKitGtkInterop.gtk_widget_realize(_container);

        var gdkWindow = WebKitGtkInterop.gtk_widget_get_window(_container);
        var xid = WebKitGtkInterop.gdk_x11_window_get_xid(gdkWindow);

        if (_configuration.InitialSource != null)
            _ = NavigateAsync(_configuration.InitialSource);

        return new PlatformHandle(xid, "LinuxWebView");
    }

    public void Destroy()
    {
        if (_webView != IntPtr.Zero)
        {
            WebKitGtkInterop.gtk_widget_destroy(_webView);
            _webView = IntPtr.Zero;
        }
        if (_container != IntPtr.Zero)
        {
            WebKitGtkInterop.gtk_widget_destroy(_container);
            _container = IntPtr.Zero;
        }
        if (_contentManager != IntPtr.Zero)
        {
            WebKitGtkInterop.g_object_unref(_contentManager);
            _contentManager = IntPtr.Zero;
        }
    }

    public Task NavigateAsync(Uri uri)
    {
        if (_webView != IntPtr.Zero)
            WebKitGtkInterop.webkit_web_view_load_uri(_webView, uri.ToString());
        return Task.CompletedTask;
    }

    public Task<string> ExecuteScriptAsync(string script)
    {
        if (_webView != IntPtr.Zero)
        {
            WebKitGtkInterop.webkit_web_view_run_javascript(_webView, script, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
            return Task.FromResult("");
        }
        return Task.FromResult(string.Empty);
    }

    public void SetZoomFactor(double zoomFactor)
    {
        if (_webView != IntPtr.Zero)
            WebKitGtkInterop.webkit_web_view_set_zoom_level(_webView, zoomFactor);
    }

    public double GetZoomFactor()
    {
        if (_webView != IntPtr.Zero)
            return WebKitGtkInterop.webkit_web_view_get_zoom_level(_webView);
        return 1.0;
    }

    public bool CanGoBack() => _webView != IntPtr.Zero && WebKitGtkInterop.webkit_web_view_can_go_back(_webView);
    public bool CanGoForward() => _webView != IntPtr.Zero && WebKitGtkInterop.webkit_web_view_can_go_forward(_webView);
    public void GoBack() { if (_webView != IntPtr.Zero) WebKitGtkInterop.webkit_web_view_go_back(_webView); }
    public void GoForward() { if (_webView != IntPtr.Zero) WebKitGtkInterop.webkit_web_view_go_forward(_webView); }
    public void Reload() { if (_webView != IntPtr.Zero) WebKitGtkInterop.webkit_web_view_reload(_webView); }

    public void AddWebResourceFilter(string filter)
    {
        _logger?.LogDebug("Web resource filter requested: {Filter} (not implemented yet)", filter);
    }

    private void ApplyConfiguration()
    {
        var settings = WebKitGtkInterop.webkit_web_view_get_settings(_webView);
        WebKitGtkInterop.webkit_settings_set_enable_developer_extras(settings, _configuration.EnableDevTools);
        foreach (var option in _configuration.PlatformOptions)
            ApplyPlatformOption(settings, option.Key, option.Value);
        if (Math.Abs(_configuration.InitialZoomFactor - 1.0) > 0.001)
            SetZoomFactor(_configuration.InitialZoomFactor);
    }

    private void ApplyPlatformOption(IntPtr settings, string key, object value)
    {
        try
        {
            switch (key.ToLowerInvariant())
            {
                case "enablejavascript":
                    if (value is bool enableJs) WebKitGtkInterop.webkit_settings_set_enable_javascript(settings, enableJs);
                    break;
                case "javascriptcanaccessclipboard":
                    if (value is bool clipboardAccess) WebKitGtkInterop.webkit_settings_set_javascript_can_access_clipboard(settings, clipboardAccess);
                    break;
                case "enablemediastream":
                    if (value is bool mediaStream) WebKitGtkInterop.webkit_settings_set_enable_media_stream(settings, mediaStream);
                    break;
                case "enablesmoothscrolling":
                    if (value is bool smoothScrolling) WebKitGtkInterop.webkit_settings_set_enable_smooth_scrolling(settings, smoothScrolling);
                    break;
                case "allowfileaccessfromfileurls":
                    if (value is bool fileAccess) WebKitGtkInterop.webkit_settings_set_allow_file_access_from_file_urls(settings, fileAccess);
                    break;
                case "disablewebsecurity":
                    if (value is bool disableSecurity) WebKitGtkInterop.webkit_settings_set_disable_web_security(settings, disableSecurity);
                    break;
                case "useragent":
                    if (value is string userAgent) WebKitGtkInterop.webkit_settings_set_user_agent(settings, userAgent);
                    break;
                default:
                    _logger?.LogWarning("Unknown platform option: {Key}", key);
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger?.LogWarning(ex, "Failed to apply platform option {Key} = {Value}", key, value);
        }
    }

    private void SetupScriptMessaging()
    {
        if (_contentManager != IntPtr.Zero)
        {
            WebKitGtkInterop.webkit_user_content_manager_register_script_message_handler(_contentManager, "MakingAvalonia");
            var initScript = @"window.__receiveMessageCallbacks = [];window.__dispatchMessageCallback = function(message) {window.__receiveMessageCallbacks.forEach(function(callback) { callback(message); });};window.external = {sendMessage: function(message) {window.webkit.messageHandlers.MakingAvalonia.postMessage(message);},receiveMessage: function(callback) {window.__receiveMessageCallbacks.push(callback);} };";
            var script = WebKitGtkInterop.webkit_user_script_new(initScript, WebKitGtkInterop.WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WebKitGtkInterop.WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, IntPtr.Zero, IntPtr.Zero);
            WebKitGtkInterop.webkit_user_content_manager_add_script(_contentManager, script);
        }
    }

    private void CreateContainer()
    {
        _container = WebKitGtkInterop.gtk_window_new(WebKitGtkInterop.GTK_WINDOW_TOPLEVEL);
        if (_container == IntPtr.Zero)
            throw new InvalidOperationException("Failed to create GTK container window");
        WebKitGtkInterop.gtk_window_set_default_size(_container, 800, 600);
    }
}